﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Description;
using System.Text;
using System.Xml;
using CodeDom.SitecoreService;

namespace CodeDom
{
  public class SitecoreConnection
  {
    private readonly string _serviceAddress;
    private readonly string _userName;
    private readonly string _password;
    private readonly Guid STANDARDTEMPLATEID = new Guid("{1930BBEB-7805-471A-A3BE-4858AC7CF696}");

    private VisualSitecoreServiceSoapClient _client;
    private Credentials _credentials;
    private Dictionary<string, Item> _itemCache = new Dictionary<string,Item>();
    private Template _standardTemplate;
    private readonly IDictionary<Guid, Template> _templateCache = new Dictionary<Guid, Template>();

    public SitecoreConnection(string serviceAddress, string userName, string password)
    {
      _serviceAddress = serviceAddress;
      _userName = userName;
      _password = password;
    }

    internal VisualSitecoreServiceSoapClient Client
    {
      get {
        if (_client == null)
        {
          _client = new VisualSitecoreServiceSoapClient("Visual Sitecore ServiceSoap", _serviceAddress);
        }
        return _client; 
      }
    }
    
    public Credentials Credentials {
      get {
        if (_credentials == null)
        {
          _credentials = new Credentials();
          _credentials.UserName = _userName;
          _credentials.Password = _password;
        }
        return _credentials;
      }
    }

    /// <summary>
    /// Gets the item.
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns></returns>
    public Item GetItem(string path) {
      if (ItemCache.ContainsKey(path))
        return ItemCache[path];
      
      Item item = new Item(GetItemXML(path), this);
      ItemCache.Add(path, item);
      return item;
    }

    private Dictionary<String, Item> ItemCache
    {
      get { return _itemCache; }
    }

    /// <summary>
    /// Gets the full XML for a given ID
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"><c>InvalidOperationException</c>.</exception>
    private XmlElement GetItemXML(string path)
    {
      XmlNode node = GetXML(path);
      XmlElement element = (XmlElement)node.SelectSingleNode("/data/data/item");
      if (element == null)
        throw new InvalidOperationException(string.Format("Could not find data for id {0}", path));
      return element;
    }

    /// <summary>
    /// Gets the XML for a given ID from Sitecore
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns></returns>
    private XmlNode GetXML(string path)
    {
      XmlNode node = Client.GetXML(path, false, "master", Credentials);
      AssertNodeStatusIsOK(node);
      return node;
    }

    /// <summary>
    /// Asserts the status.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <exception cref="InvalidOperationException"><c>InvalidOperationException</c>.</exception>
    private void AssertNodeStatusIsOK(XmlNode node)
    {
      if (node.SelectSingleNode("/status[text()='ok']") == null)
        throw new InvalidOperationException(string.Format("Failed to get XML, status: '{0}'", node.SelectSingleNode("/status/text()").Value));
    }

    public IEnumerable<Template> GetTemplates(string rootPath)
    {
      Item item = GetItem(rootPath);
      return GetTemplates(item);
    }

    /// <summary>
    /// Gets the templates.
    /// </summary>
    private IEnumerable<Template> GetTemplates(Item parentItem)
    {
      foreach (Item childItem in parentItem.Children)
      {
        if (Template.IsValid(childItem))
          yield return new Template(childItem);
        else
        {
          foreach (Template template in GetTemplates(childItem))
            yield return template;
        }
      }
      yield break;
    }

    /// <summary>
    /// Gets the child items.
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns></returns>
    /// <exception cref="ArgumentException">id</exception>
    /// <exception cref="InvalidOperationException">Incorrect XML returned from GetChildren</exception>
    public IEnumerable<Item> GetChildItems(string path)
    {
      XmlNode children = Client.GetChildren(path, "master", Credentials);
      if (children == null)
        throw new ArgumentException(string.Format("Cannot load children for id {0}", path), "id");

      IEnumerable nodes = children.SelectNodes("*");
      if (nodes == null)
        throw new InvalidOperationException("Incorrect XML returned from GetChildren");

      foreach (XmlNode child in nodes)
      {
        string childId = child.Attributes["id"].Value;
        yield return GetItem(childId);
      }
      yield break;
    }

    /// <summary>
    /// Gets a template by ID
    /// </summary>
    /// <param name="id">The id.</param>
    /// <returns></returns>
    /// <exception cref="ArgumentException">id</exception>
    public Template GetTemplate(Guid id)
    {
      if (_templateCache.ContainsKey(id))
        return _templateCache[id];
        
      Item item = GetItem(id.ToString());
      if (item == null)
        return null;
      if (!Template.IsValid(item))
        throw new ArgumentException(string.Format("Invalid template {0} is not a template", id), "id");
      Template template = new Template(item);
      _templateCache.Add(template.ID, template);
      return template;
    }

    /// <summary>
    /// Gets the template field by id
    /// </summary>
    /// <param name="id">The id.</param>
    /// <returns></returns>
    /// <exception cref="ArgumentException">id</exception>
    public TemplateField GetTemplateField(Guid id)
    {
      Item item = (Item)GetItem(id.ToString());
      if (item == null || !TemplateField.IsValid(item))
        throw new ArgumentException(string.Format("Invalid template field. Item {0} is not a template field", id), "id");
      return new TemplateField(item);
    }


    public Template StandardTemplate
    {
      get
      {
        if (_standardTemplate == null)
        {
          Item item = GetItem(STANDARDTEMPLATEID.ToString());
          if (item == null)
            throw new InvalidOperationException("Cannot load Standard Template");
          _standardTemplate = new Template(item);
        }

        return _standardTemplate;
      }
    }

    public bool DerivesFromStandardTemplate(Guid templateID)
    {
      return templateID != StandardTemplate.ID && !StandardTemplate.BaseTemplatesIDs.Contains(templateID);
    }
  }
}
